/**
 * @author		Ben Layet
 * @version 	1.0
 * @date		23 Sep 2008
 */
package org.ignoramus.common.map;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;


/**
 * List of review cards.
 */
public class ObservableTreeMap<K,V> extends TreeMap<K,V>
{

	/** The list of listeners. */
	private final List<MapListener<K,V>> listeners = Collections.synchronizedList(new ArrayList<MapListener<K,V>>());
	

	/**
	 * Adds a listener.
	 * @param l the listener to add.
	 */
	public void addMapListener(MapListener<K,V> l)
	{
		listeners.add(l);
	}

	/**
	 * Removes a listener.
	 * @param l the listener to remove.
	 */
	public void removeMapListener(MapListener<K,V> l)
	{
		listeners.remove(l);
	}


	/**
	 * Notify the listeners of a modification.
	 */
	private void notifyListeners()
	{
		//create the event
		MapEvent<K,V> evt = new MapEvent<K,V>(this);
		//loop through the listeners
		synchronized (listeners)
		{
			for (Iterator<MapListener<K,V>> iterator = listeners.iterator(); iterator.hasNext();)
			{
				iterator.next().mapModified(evt);
			}
		}
	}

	// Modification Operations

    /**
     * Associates the specified value with the specified key in this map
     * (optional operation).  If the map previously contained a mapping for
     * the key, the old value is replaced by the specified value.  (A map
     * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only
     * if {@link #containsKey(Object) m.containsKey(k)} would return
     * <tt>true</tt>.)
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>,
     *         if the implementation supports <tt>null</tt> values.)
     * @throws UnsupportedOperationException if the <tt>put</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the class of the specified key or value
     *         prevents it from being stored in this map
     * @throws NullPointerException if the specified key or value is null
     *         and this map does not permit null keys or values
     * @throws IllegalArgumentException if some property of the specified key
     *         or value prevents it from being stored in this map
     */
	public V put(K key, V value)
	{
		V ret = super.put(key, value);
		notifyListeners();
		return ret;
	}

	/**
     * Removes the mapping for a key from this map if it is present
     * (optional operation).   More formally, if this map contains a mapping
     * from key <tt>k</tt> to value <tt>v</tt> such that
     * <code>(key==null ?  k==null : key.equals(k))</code>, that mapping
     * is removed.  (The map can contain at most one such mapping.)
     *
     * <p>Returns the value to which this map previously associated the key,
     * or <tt>null</tt> if the map contained no mapping for the key.
     *
     * <p>If this map permits null values, then a return value of
     * <tt>null</tt> does not <i>necessarily</i> indicate that the map
     * contained no mapping for the key; it's also possible that the map
     * explicitly mapped the key to <tt>null</tt>.
     *
     * <p>The map will not contain a mapping for the specified key once the
     * call returns.
     *
     * @param key key whose mapping is to be removed from the map
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     * @throws UnsupportedOperationException if the <tt>remove</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the key is of an inappropriate type for
     *         this map (optional)
     * @throws NullPointerException if the specified key is null and this
     *         map does not permit null keys (optional)
     */
    public V remove(Object key)
	{
		//execute the modification
		V ret = super.remove(key);
		//notify the listeners
		notifyListeners();
		//return the result
		return ret;
	}
    

    // Bulk Operations

    /**
     * Copies all of the mappings from the specified map to this map
     * (optional operation).  The effect of this call is equivalent to that
     * of calling {@link #put(Object,Object) put(k, v)} on this map once
     * for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
     * specified map.  The behaviour of this operation is undefined if the
     * specified map is modified while the operation is in progress.
     *
     * @param m mappings to be stored in this map
     * @throws UnsupportedOperationException if the <tt>putAll</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the class of a key or value in the
     *         specified map prevents it from being stored in this map
     * @throws NullPointerException if the specified map is null, or if
     *         this map does not permit null keys or values, and the
     *         specified map contains null keys or values
     * @throws IllegalArgumentException if some property of a key or value in
     *         the specified map prevents it from being stored in this map
     */
    public void putAll(Map<? extends K, ? extends V> m)
    {
		//execute the modification
    	super.putAll(m);
		//notify the listeners
    	notifyListeners();
    }
    

    /**
     * Removes all of the mappings from this map (optional operation).
     * The map will be empty after this call returns.
     *
     * @throws UnsupportedOperationException if the <tt>clear</tt> operation
     *         is not supported by this map
     */
    public void clear()
    {
		//execute the modification
    	super.clear();
		//notify the listeners
    	notifyListeners();
    }
}
